//
// Code adapted by Bodmer as an example for TFT_eSPI, this runs on any
// TFT_eSPI compatible processor so ignore the technical limitations
// detailed in the original header below. Assorted changes have been
// made including removal of the display mirror kludge.

//--------------------------------------------------------------------------
// Uncanny eyes for Adafruit 1.5" OLED (product #1431) or 1.44" TFT LCD
// (#2088).  Works on PJRC Teensy 3.x and on Adafruit M0 and M4 boards
// (Feather, Metro, etc.).  This code uses features specific to these
// boards and WILL NOT work on normal Arduino or other boards!
//
// SEE FILE "config.h" FOR MOST CONFIGURATION (graphics, pins, display type,
// etc).  Probably won't need to edit THIS file unless you're doing some
// extremely custom modifications.
//
// Adafruit invests time and resources providing this open source code,
// please support Adafruit and open-source hardware by purchasing products
// from Adafruit!
//
// Written by Phil Burgess / Paint Your Dragon for Adafruit Industries.
// MIT license.  SPI FIFO insight from Paul Stoffregen's ILI9341_t3 library.
// Inspired by David Boccabella's (Marcwolf) hybrid servo/OLED eye concept.
//--------------------------------------------------------------------------

// Initialise eyes ---------------------------------------------------------
void initEyes(void) {
  Serial.println("Initialise eye objects");

  // Initialise eye objects based on eyeInfo list in config.h:
  for (uint8_t e = 0; e < NUM_EYES; e++) {
    Serial.print("Create display #");
    Serial.println(e);

    eye[e].tft_cs = eyeInfo[e].select;
    eye[e].blink.state = NOBLINK;
    eye[e].xposition = eyeInfo[e].xposition;

    pinMode(eye[e].tft_cs, OUTPUT);
    digitalWrite(eye[e].tft_cs, LOW);

    // Also set up an individual eye-wink pin if defined:
    if (eyeInfo[e].wink >= 0) pinMode(eyeInfo[e].wink, INPUT_PULLUP);
  }

#if defined(BLINK_PIN) && (BLINK_PIN >= 0)
  pinMode(BLINK_PIN, INPUT_PULLUP);  // Ditto for all-eyes blink pin
#endif
}

#ifdef AUTOBLINK
uint32_t timeOfLastBlink = 0L, timeToNextBlink = 0L;
#endif

// AUTONOMOUS IRIS SCALING (if no photocell or dial) -----------------------

#if !defined(LIGHT_PIN) || (LIGHT_PIN < 0)

// EYE-RENDERING FUNCTION --------------------------------------------------
void drawEye(  // Renders one eye.  Inputs must be pre-clipped & valid.
  // Use native 32 bit variables where possible as this is 10% faster!
  uint8_t e,         // Eye array index; 0 or 1 for left/right
  uint32_t iScale,   // Scale factor for iris
  uint32_t scleraX,  // First pixel X offset into sclera image
  uint32_t scleraY,  // First pixel Y offset into sclera image
  uint32_t uT,       // Upper eyelid threshold value
  uint32_t lT) {     // Lower eyelid threshold value

  uint32_t screenX, screenY, scleraXsave;
  int32_t irisX, irisY;
  uint32_t p, a;
  uint32_t d;

  uint32_t pixels = 0;

  // Set up raw pixel dump to entire screen.  Although such writes can wrap
  // around automatically from end of rect back to beginning, the region is
  // reset on each frame here in case of an SPI glitch.
  digitalWrite(eye[e].tft_cs, LOW);
  tft.startWrite();
  tft.setAddrWindow(eye[e].xposition, 0, 128, 128);

  // Now just issue raw 16-bit values for every pixel...

  scleraXsave = scleraX;  // Save initial X value to reset on each line
  irisY = scleraY - (SCLERA_HEIGHT - IRIS_HEIGHT) / 2;

  // Eyelid image is left<>right swapped for two displays
  uint16_t lidX = 0;
  uint16_t dlidX = -1;
  if (e) dlidX = 1;
  for (screenY = 0; screenY < SCREEN_HEIGHT; screenY++, scleraY++, irisY++) {
    scleraX = scleraXsave;
    irisX = scleraXsave - (SCLERA_WIDTH - IRIS_WIDTH) / 2;
    if (e) lidX = 0;
    else lidX = SCREEN_WIDTH - 1;
    for (screenX = 0; screenX < SCREEN_WIDTH; screenX++, scleraX++, irisX++, lidX += dlidX) {
      if ((pgm_read_byte(lower + screenY * SCREEN_WIDTH + lidX) <= lT) || (pgm_read_byte(upper + screenY * SCREEN_WIDTH + lidX) <= uT)) {  // Covered by eyelid
        p = 0;
      } else if ((irisY < 0) || (irisY >= IRIS_HEIGHT) || (irisX < 0) || (irisX >= IRIS_WIDTH)) {  // In sclera
        p = pgm_read_word(sclera + scleraY * SCLERA_WIDTH + scleraX);
      } else {                                                           // Maybe iris...
        p = pgm_read_word(polar + irisY * IRIS_WIDTH + irisX);           // Polar angle/dist
        d = (iScale * (p & 0x7F)) / 128;                                 // Distance (Y)
        if (d < IRIS_MAP_HEIGHT) {                                       // Within iris area
          a = (IRIS_MAP_WIDTH * (p >> 7)) / 512;                         // Angle (X)
          p = pgm_read_word(iris + d * IRIS_MAP_WIDTH + a);              // Pixel = iris
        } else {                                                         // Not in iris
          p = pgm_read_word(sclera + scleraY * SCLERA_WIDTH + scleraX);  // Pixel = sclera
        }
      }
      *(&pbuffer[dmaBuf][0] + pixels++) = p >> 8 | p << 8;

      if (pixels >= BUFFER_SIZE) {
        yield();
#ifdef USE_DMA
        tft.pushPixelsDMA(&pbuffer[dmaBuf][0], pixels);
        dmaBuf = !dmaBuf;
#else
        tft.pushPixels(pbuffer, pixels);
#endif
        pixels = 0;
      }
    }
  }

  if (pixels) {
#ifdef USE_DMA
    tft.pushPixelsDMA(&pbuffer[dmaBuf][0], pixels);
#else
    tft.pushPixels(pbuffer, pixels);
#endif
  }
  tft.endWrite();
  digitalWrite(eye[e].tft_cs, HIGH);
}

// Process motion for a single frame of left or right eye
void addWink(uint16_t iScale, int16_t eyeX, int16_t eyeY)  // Iris scale (0-1023)
{
  static uint8_t eyeIndex = 0;  // eye[] array counter

  uint32_t t = micros();  // Time at start of function

  if (++eyeIndex >= NUM_EYES) eyeIndex = 0;  // Cycle through eyes, 1 per call

    // X/Y movement

    // Autonomous X/Y eye motion
    // Periodically initiates motion to a new random point, random speed,
    // holds there for random period until next motion.

    // Blinking
#ifdef AUTOBLINK
  // Similar to the autonomous eye movement above -- blink start times
  // and durations are random (within ranges).
  if ((t - timeOfLastBlink) >= timeToNextBlink) {  // Start new blink?
    timeOfLastBlink = t;
    uint32_t blinkDuration = random(36000, 72000);  // ~1/28 - ~1/14 sec
    // Set up durations for both eyes (if not already winking)
    for (uint8_t e = 0; e < NUM_EYES; e++) {
      if (eye[e].blink.state == NOBLINK) {
        eye[e].blink.state = ENBLINK;
        eye[e].blink.startTime = t;
        eye[e].blink.duration = blinkDuration;
      }
    }
    timeToNextBlink = blinkDuration * 3 + random(4000000);
  }
#endif

  if (eye[eyeIndex].blink.state) {  // Eye currently blinking?
    // Check if current blink state time has elapsed
    if ((t - eye[eyeIndex].blink.startTime) >= eye[eyeIndex].blink.duration) {
      // Yes -- increment blink state, unless...
      if ((eye[eyeIndex].blink.state == ENBLINK) && (  // Enblinking and...
#if defined(BLINK_PIN) && (BLINK_PIN >= 0)
            (digitalRead(BLINK_PIN) == LOW) ||  // blink or wink held...
#endif
            ((eyeInfo[eyeIndex].wink >= 0) && digitalRead(eyeInfo[eyeIndex].wink) == LOW))) {
        // Don't advance state yet -- eye is held closed instead
      } else {                                        // No buttons, or other state...
        if (++eye[eyeIndex].blink.state > DEBLINK) {  // Deblinking finished?
          eye[eyeIndex].blink.state = NOBLINK;        // No longer blinking
        } else {                                      // Advancing from ENBLINK to DEBLINK mode
          eye[eyeIndex].blink.duration *= 2;          // DEBLINK is 1/2 ENBLINK speed
          eye[eyeIndex].blink.startTime = t;
        }
      }
    }
  } else {  // Not currently blinking...check buttons!
#if defined(BLINK_PIN) && (BLINK_PIN >= 0)
    if (digitalRead(BLINK_PIN) == LOW) {
      // Manually-initiated blinks have random durations like auto-blink
      uint32_t blinkDuration = random(36000, 72000);
      for (uint8_t e = 0; e < NUM_EYES; e++) {
        if (eye[e].blink.state == NOBLINK) {
          eye[e].blink.state = ENBLINK;
          eye[e].blink.startTime = t;
          eye[e].blink.duration = blinkDuration;
        }
      }
    } else
#endif
      if ((eyeInfo[eyeIndex].wink >= 0) && (digitalRead(eyeInfo[eyeIndex].wink) == LOW)) {  // Wink!
      eye[eyeIndex].blink.state = ENBLINK;
      eye[eyeIndex].blink.startTime = t;
      eye[eyeIndex].blink.duration = random(45000, 90000);
    }
  }

  // Eyelids are rendered using a brightness threshold image.  This same
  // map can be used to simplify another problem: making the upper eyelid
  // track the pupil (eyes tend to open only as much as needed -- e.g. look
  // down and the upper eyelid drops).  Just sample a point in the upper
  // lid map slightly above the pupil to determine the rendering threshold.
  static uint8_t uThreshold = 128;
  uint8_t lThreshold, n;

  uThreshold = lThreshold = 0;


  // The upper/lower thresholds are then scaled relative to the current
  // blink position so that blinks work together with pupil tracking.
  if (eye[eyeIndex].blink.state) {  // Eye currently blinking?
    uint32_t s = (t - eye[eyeIndex].blink.startTime);
    if (s >= eye[eyeIndex].blink.duration) s = 255;   // At or past blink end
    else s = 255 * s / eye[eyeIndex].blink.duration;  // Mid-blink
    s = (eye[eyeIndex].blink.state == DEBLINK) ? 1 + s : 256 - s;
    n = (uThreshold * s + 254 * (257 - s)) / 256;
    lThreshold = (lThreshold * s + 254 * (257 - s)) / 256;
  } else {
    n = uThreshold;
  }
  drawEye(eyeIndex, iScale, eyeX, eyeY, n, lThreshold);
  // Pass all the derived values to the eye-rendering function:
}

uint16_t newScale = IRIS_MIN;
uint16_t oldScale = IRIS_MIN;
uint16_t curScale = oldScale;

uint16_t addScale() {
  if (curScale == newScale) {
    oldScale = newScale;
    newScale = (IRIS_MIN + IRIS_MAX) / 2 + random(IRIS_MAX - (IRIS_MIN + IRIS_MAX) / 2);
  }
  if (newScale > oldScale) {
    curScale++;
  } else if (newScale < oldScale) {
    curScale--;
  } else {
    curScale = newScale;
  }
  return curScale;
}

void updateEye(int16_t eyeX, int16_t eyeY) {
  uint16_t iScale = addScale();
  addWink(iScale, eyeX, eyeY);
}

void circleTest(uint32_t count, int16_t eyeX, int16_t eyeY) {
  while (count > 0) {
    /* code */
    updateEye(eyeX, eyeY);  // Draw frame w/interim iris scale value
    count--;
  }
}

void updateEyeTest(void) {
  circleTest(200, 0, 0);    // Draw frame w/interim iris scale value
  circleTest(200, 50, 0);   // Draw frame w/interim iris scale value
  circleTest(200, 0, 50);   // Draw frame w/interim iris scale value
  circleTest(200, 50, 50);  // Draw frame w/interim iris scale value
  circleTest(200, 0, 0);    // Draw frame w/interim iris scale value
  circleTest(200, 0, 25);   // Draw frame w/interim iris scale value
  circleTest(200, 25, 0);   // Draw frame w/interim iris scale value
  circleTest(200, 25, 25);  // Draw frame w/interim iris scale value
  circleTest(200, 25, 50);  // Draw frame w/interim iris scale value
  circleTest(200, 50, 25);  // Draw frame w/interim iris scale value
  circleTest(200, 25, 50);  // Draw frame w/interim iris scale value
  circleTest(200, 50, 50);  // Draw frame w/interim iris scale value
}


#endif  // !LIGHT_PIN
